﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;

namespace Skywave.Reporting
{
    public class HtmlReport
    {
        public event EventHandler<HtmlReportFieldGetValueEventArgs> FieldGetValue;

        private string _TemplateDataItemPath;
        /// <summary>
        /// ...
        /// </summary>
        public string TemplateDataItemPath
        {
            get { return _TemplateDataItemPath; }
            set { _TemplateDataItemPath = value; }
        }

        private System.IO.Stream _TemplateDataItemStream = null;
        /// <summary>
        /// ...
        /// </summary>
        public System.IO.Stream TemplateDataItemStream
        {
            get { return _TemplateDataItemStream; }
            set { _TemplateDataItemStream = value; }
        }

        private string _TemplateDataItem = null;
        /// <summary>
        /// ...
        /// </summary>
        public string TemplateDataItem
        {
            get { return _TemplateDataItem; }
            set { _TemplateDataItem = value; }
        }

        private string _TemplatePagePath;
        /// <summary>
        /// ...
        /// </summary>
        public string TemplatePagePath
        {
            get { return _TemplatePagePath; }
            set { _TemplatePagePath = value; }
        }

        private System.IO.Stream _TemplatePageStream = null;
        /// <summary>
        /// ...
        /// </summary>
        public System.IO.Stream TemplatePageStream
        {
            get { return _TemplatePageStream; }
            set { _TemplatePageStream = value; }
        }

        private string _TemplatePage = null;
        /// <summary>
        /// ...
        /// </summary>
        public string TemplatePage
        {
            get { return _TemplatePage; }
            set { _TemplatePage = value; }
        }

        /// <summary>
        /// ...
        /// </summary>
        public string StorageDirectoryPath
        {
            get { return (StorageDirectory == null) ? "" : StorageDirectory.FullName; }
            set { StorageDirectory = new System.IO.DirectoryInfo(value); }
        }

        private System.IO.DirectoryInfo _StorageDirectory;
        /// <summary>
        /// ...
        /// </summary>
        public System.IO.DirectoryInfo StorageDirectory
        {
            get { return _StorageDirectory; }
            set { _StorageDirectory = value; }
        }

        private IEnumerable _Data;
        /// <summary>
        /// ...
        /// </summary>
        public IEnumerable Data
        {
            get { return _Data; }
            set { _Data = value; }
        }

        private List<HtmlReportParsedField> _LoadedTemplateFields;
        /// <summary>
        /// ...
        /// </summary>
        public List<HtmlReportParsedField> LoadedTemplateFields
        {
            get { return _LoadedTemplateFields; }
        }

        /// <summary>
        /// ...
        /// </summary>
        public List<HtmlReportParsedField> LoadedTemplateFields_List
        {
            get { return _LoadedTemplateFields_List(_LoadedTemplateFields); }
        }

        private List<HtmlReportParsedField> _LoadedTemplateFields_List(List<HtmlReportParsedField> input)
        {
            List<HtmlReportParsedField> r = new List<HtmlReportParsedField>();
            r.AddRange(input);
            foreach (var fe1 in input.Where(q => q.Field.ParsedFields != null && q.Field.ParsedFields.Count > 0))
                r.AddRange(_LoadedTemplateFields_List(fe1.Field.ParsedFields));
            //
            return r;
        }

        private string _GeneratedHtml;
        /// <summary>
        /// ...
        /// </summary>
        public string GeneratedHtml
        {
            get { return _GeneratedHtml; }
        }

        public string Generate(bool writeOutputFile = true)
        {
            if (string.IsNullOrEmpty(TemplateDataItem) && TemplateDataItemStream == null && !System.IO.File.Exists(TemplateDataItemPath))
                throw new ArgumentException("Template should be specified.");
            if (Data == null)
                throw new ArgumentException("Data should not be null.");
            //
            if (!StorageDirectory.Exists)
                StorageDirectory.Create();
            //
            string templateDataItemContent;
            if (!string.IsNullOrEmpty(TemplateDataItem))
                templateDataItemContent = TemplateDataItem;
            else if (TemplateDataItemStream != null)
            {
                if (TemplateDataItemStream.CanSeek)
                    TemplateDataItemStream.Position = 0;
                using (System.IO.StreamReader sr1 = new System.IO.StreamReader(TemplateDataItemStream))
                {
                    templateDataItemContent = sr1.ReadToEnd();
                    sr1.Close();
                }
            }
            else
            {
                using (System.IO.StreamReader sr1 = new System.IO.StreamReader(TemplateDataItemPath))
                {
                    templateDataItemContent = sr1.ReadToEnd();
                    sr1.Close();
                }
                //
                System.IO.DirectoryInfo dirTemplateRelativeFolder = new System.IO.DirectoryInfo(System.IO.Path.GetDirectoryName(TemplateDataItemPath) + "\\" + System.IO.Path.GetFileNameWithoutExtension(TemplateDataItemPath));
                if (dirTemplateRelativeFolder.Exists)
                    IO.PathHelper.CopyAll(dirTemplateRelativeFolder, new System.IO.DirectoryInfo(StorageDirectory.FullName), true, IO.PathHelper.CopyMode.OverwriteIfNewer);
            }
            //
            string templatePageContent = "";
            if (!string.IsNullOrEmpty(TemplatePage))
                templatePageContent = TemplatePage;
            else if (TemplatePageStream != null)
            {
                if (TemplatePageStream.CanSeek)
                    TemplatePageStream.Position = 0;
                using (System.IO.StreamReader sr1 = new System.IO.StreamReader(TemplatePageStream))
                {
                    templatePageContent = sr1.ReadToEnd();
                    sr1.Close();
                }
            }
            else if (!string.IsNullOrEmpty(TemplatePagePath) && System.IO.File.Exists(TemplatePagePath))
            {
                using (System.IO.StreamReader sr1 = new System.IO.StreamReader(TemplatePagePath))
                {
                    templatePageContent = sr1.ReadToEnd();
                    sr1.Close();
                }
                //
                System.IO.DirectoryInfo dirTemplateRelativeFolder = new System.IO.DirectoryInfo(System.IO.Path.GetDirectoryName(TemplatePagePath) + "\\" + System.IO.Path.GetFileNameWithoutExtension(TemplatePagePath));
                if (dirTemplateRelativeFolder.Exists)
                    IO.PathHelper.CopyAll(dirTemplateRelativeFolder, new System.IO.DirectoryInfo(StorageDirectory.FullName), true, IO.PathHelper.CopyMode.OverwriteIfNewer);
            }
            //
            string r_Content;
            _LoadedTemplateFields = ParseFields(ref templateDataItemContent, this);
            //
            System.IO.FileInfo r_Info = new System.IO.FileInfo(IO.FileHelper.GetFreeFilePath("Report_", ".html", StorageDirectory.FullName, 1, true));
            System.IO.DirectoryInfo r_Info_RelativeFolder = new System.IO.DirectoryInfo(r_Info.DirectoryName + "\\" + System.IO.Path.GetFileNameWithoutExtension(r_Info.FullName));
            //
            object field_Value;
            r_Content = templateDataItemContent;
            //
            int iField = 1;
            foreach (HtmlReportParsedField feParsedField in _LoadedTemplateFields)
            {
                field_Value = GetFieldValue(feParsedField.Field, Data);
                //
                string field_Content = "";
                HtmlReportFieldGetContentArgs eArgs2 = new HtmlReportFieldGetContentArgs()
                {
                    Report = this,
                    DataItem = Data,
                    Value = field_Value,
                    ContentsDirectory = r_Info_RelativeFolder,
                    IndexField = iField
                };
                field_Content = feParsedField.Field.GetContent(eArgs2);
                //
                if (eArgs2.IndexField > iField)
                    iField = eArgs2.IndexField;
                //
                r_Content = r_Content.Replace(feParsedField.ParsedText_ReplaceKey, field_Content);
                //
                iField++;
            }
            //
            if (!string.IsNullOrEmpty(templatePageContent))
                r_Content = templatePageContent.Replace("%dataItems%", r_Content);
            //
            _GeneratedHtml = r_Content;
            //
            if (writeOutputFile)
            {
                using (System.IO.StreamWriter sw1 = new System.IO.StreamWriter(r_Info.FullName))
                {
                    sw1.Write(r_Content);
                    //
                    sw1.Close();
                }
            }
            //
            return r_Info.FullName;
        }

        public object GetFieldValue(HtmlReportField feField, object feDataItem)
        {
            object r = null;
            HtmlReportFieldGetValueEventArgs eArgs1;
            //
            if (!string.IsNullOrEmpty(feField.BindingPath))
            {
                if (feField.BindingPath == ".")
                    r = feDataItem;
                else
                    r = Reflection.Evaluator.Eval(feDataItem, feField.BindingPath);
            }
            //
            if (FieldGetValue != null)
            {
                eArgs1 = new HtmlReportFieldGetValueEventArgs(feDataItem, feField);
                eArgs1.Value = r;
                FieldGetValue(this, eArgs1);
                r = eArgs1.Value;
            }

            return r;
        }

        //OLD METHOD 1: Regex regex_Field = new Regex(@"#(?<type>\d+)@(?<id>[^#^?]+)[?]*(?<options>(?<key>[^#^?^=^&]+)[=](?<value>[^#^?^=^&]+)[&]*)*#");
        //OLD METHOD 2: Regex regex_Field = new Regex(@"((\<skywave:ReportField(?!/>)[^<]*(/>){1})|(\<skywave:ReportField((?!\</skywave:ReportField>)(.|\s))*(\</skywave:ReportField\>){1}))");
        private const string regex_Field_Pattern =
                "(" +
                    "<skywave:ReportField[^<>]*/>" +
                ")" +
                "|" +
                "(" +
                    "(" +
                        @"(?<Open>\<skywave:ReportField.*[^/]>)" +
                        @"((?![<]/skywave:ReportField>)(.|\s))*" +
                    ")+" +
                    "(" +
                        @"(?<Close-Open>\</skywave:ReportField>)" +
                        @"((?![<]skywave:ReportField.*[^/]>)(.|\s))*" +
                    ")+" +
                ")"
                ;
        static Regex regex_Field = new Regex(regex_Field_Pattern);
        //
        public static List<HtmlReportParsedField> ParseFields(ref string toParse, object parent)
        {
            string s1;
            int i1;
            HtmlReportField field1;
            HtmlReportParsedField parsed;
            List<HtmlReportParsedField> r = new List<HtmlReportParsedField>();
            //foreach (Match fe1 in regex_Field.Matches(toParse))
            //{
            //    i1 = fe1.Value.LastIndexOf(@"</skywave:ReportField");
            //    if (i1 >= 0)
            //        s1 = fe1.Value.Substring(0, i1 + @"</skywave:ReportField".Length + 1);
            //    else
            //        s1 = fe1.Value;
            //    //
            //    field1 = HtmlReportField.Create(s1, parent);
            //    //
            //    parsed = r.SingleOrDefault(q => q.Field.Id == field1.Id);
            //    if (parsed == null)
            //    {
            //        parsed = new HtmlReportParsedField() { Field = field1, ParsedText = s1, ParsedText_ReplaceKey = string.Format("F_{0}_A", field1.Id) };
            //        r.Add(parsed);
            //    }
            //    //
            //    s1 = parsed.ParsedText_ReplaceKey;
            //    if (i1 > 0) //Find fields in the remaining part of finded regex (if any)
            //    {
            //        s1 += fe1.Value.Substring(i1 + @"</skywave:ReportField".Length + 1);
            //        foreach (Match fe2 in regex_Field.Matches(s1))
            //        {
            //            field1 = HtmlReportField.Create(fe2.Value, parent);
            //            //
            //            parsed = r.SingleOrDefault(q => q.Field.Id == field1.Id);
            //            if (parsed == null)
            //            {
            //                parsed = new HtmlReportParsedField() { Field = field1, ParsedText = fe2.Value, ParsedText_ReplaceKey = string.Format("F_{0}_A", field1.Id) };
            //                r.Add(parsed);
            //            }
            //            //
            //            s1 = s1.Replace(fe2.Value, parsed.ParsedText_ReplaceKey);
            //        }
            //    }
            //    //
            //    toParse = toParse.Replace(fe1.Value, s1);
            //}
            foreach (string fe1 in ParseTags(toParse, "skywave:ReportField"))
            {
                s1 = fe1;
                //
                field1 = HtmlReportField.Create(s1, parent);
                //
                parsed = r.SingleOrDefault(q => q.Field.Id == field1.Id);
                if (parsed == null)
                {
                    parsed = new HtmlReportParsedField() { Field = field1, ParsedText = s1, ParsedText_ReplaceKey = string.Format("F_{0}_A", field1.Id) };
                    r.Add(parsed);
                }
                toParse = toParse.Replace(fe1, parsed.ParsedText_ReplaceKey);

                ////
                //s1 = parsed.ParsedText_ReplaceKey;
                //if (i1 > 0) //Find fields in the remaining part of finded regex (if any)
                //{
                //    s1 += fe1.Value.Substring(i1 + @"</skywave:ReportField".Length + 1);
                //    foreach (Match fe2 in regex_Field.Matches(s1))
                //    {
                //        field1 = HtmlReportField.Create(fe2.Value, parent);
                //        //
                //        parsed = r.SingleOrDefault(q => q.Field.Id == field1.Id);
                //        if (parsed == null)
                //        {
                //            parsed = new HtmlReportParsedField() { Field = field1, ParsedText = fe2.Value, ParsedText_ReplaceKey = string.Format("F_{0}_A", field1.Id) };
                //            r.Add(parsed);
                //        }
                //        //
                //        s1 = s1.Replace(fe2.Value, parsed.ParsedText_ReplaceKey);
                //    }
                //}
                ////
                //toParse = toParse.Replace(fe1.Value, s1);
            }
            //
            return r;
        }

        private static List<string> ParseTags(string toParse, string tag)
        {
            List<string> r = new List<string>();
            string parse_1 = toParse;
            int i_Tag_Start, i_Tag_Start_After;
            string tag_Start = "<" + tag;
            string tag_End_Short = "/>";
            string tag_End = string.Format("</{0}>", tag);

            while (true)
            {
                i_Tag_Start = parse_1.IndexOf(tag_Start);
                if (i_Tag_Start > 0)
                {
                    i_Tag_Start_After = i_Tag_Start + tag_Start.Length;
                    int i_Start = parse_1.Substring(i_Tag_Start_After).IndexOf("<");
                    int i_End = parse_1.Substring(i_Tag_Start_After).IndexOf(tag_End_Short);
                    if (i_End >= 0 && i_End < i_Start)
                    {
                        r.Add(parse_1.Substring(i_Tag_Start, i_Tag_Start_After + i_End + tag_End_Short.Length - i_Tag_Start));
                        parse_1 = parse_1.Substring(i_Tag_Start_After + i_End + tag_End_Short.Length);
                    }
                    else
                    {
                        StringBuilder parse_1_Copy = new StringBuilder(parse_1);
                        //
                        int i_Start_New;
                        do
                        {
                            string parse_1_Copy_String = parse_1_Copy.ToString();
                            //
                            int i_Start_New_After = i_Tag_Start_After;
                            while (true)
                            {
                                i_Start_New = parse_1_Copy_String.Substring(i_Start_New_After).IndexOf(tag_Start);
                                if (i_Start_New >= 0)
                                {
                                    i_Start_New_After += i_Start_New + tag_Start.Length;
                                    i_Start = parse_1_Copy_String.Substring(i_Start_New_After).IndexOf("<");
                                    i_End = parse_1_Copy_String.Substring(i_Start_New_After).IndexOf(tag_End_Short);
                                    if (i_End >= 0 && i_End < i_Start)
                                    {
                                        i_Start_New_After += i_End + tag_End_Short.Length;
                                    }
                                    else
                                    {
                                        i_Start_New = i_Start_New_After - tag_Start.Length - i_Tag_Start_After;
                                        break;
                                    }
                                }
                                else
                                {
                                    break;
                                }
                            }
                            //
                            i_End = parse_1_Copy_String.Substring(i_Tag_Start_After).IndexOf(tag_End);
                            //
                            if (i_Start_New >= 0 && i_Start_New < i_End)
                            {
                                for (int i1 = i_Tag_Start_After + i_Start_New; i1 <= i_Tag_Start_After + i_End + tag_End.Length; i1++)
                                {
                                    parse_1_Copy[i1] = '_';
                                }
                            }
                            else
                                break;
                        } while (i_End >= 0);
                        //
                        if (i_End >= 0)
                        {
                            r.Add(parse_1.Substring(i_Tag_Start, i_Tag_Start_After + i_End + tag_End.Length - i_Tag_Start));
                            parse_1 = parse_1.Substring(i_Tag_Start_After + i_End + tag_End.Length);
                        }
                    }
                }
                else
                    break;
            }
            //
            return r;
        }

        internal static string Parsing_FixToParse(string toParse)
        {
            return @"<?xml version='1.0' standalone='yes' ?>
             <!DOCTYPE r [
                 <!ELEMENT r (#PCDATA)>
                 <!ENTITY nbsp '&#160;'>
             ]>" + toParse;
        }

        internal static System.Xml.XmlReader Parsing_GetXmlReader(System.IO.TextReader textReader)
        {
            System.Xml.XmlReaderSettings xmlS = new System.Xml.XmlReaderSettings();
            xmlS.ProhibitDtd = false;
            System.Xml.NameTable xmlNT = new System.Xml.NameTable();
            System.Xml.XmlNamespaceManager xmlNSM = new System.Xml.XmlNamespaceManager(xmlNT);
            xmlNSM.AddNamespace("skywave", "http://skywavetech.ir/skywave/reporting");
            System.Xml.XmlParserContext xmlP = new System.Xml.XmlParserContext(null, xmlNSM, null, System.Xml.XmlSpace.None);
            return System.Xml.XmlReader.Create(textReader, xmlS, xmlP);
        }
    }
}
